package com.gradecak.alfresco.mvc.util;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


import org.alfresco.model.ContentModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.repository.datatype.TypeConverter;
import org.alfresco.service.namespace.QName;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.gradecak.alfresco.mvc.action.Action;
import com.gradecak.alfresco.mvc.domain.CMObject;
import com.gradecak.alfresco.mvc.domain.ContentObject;

public class MetadataManager {

  static private TypeConverter converter = DefaultTypeConverter.INSTANCE;

  private ServiceRegistry serviceRegistry;
  private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor;

  /**
   * handles only simple types (no associations ... complex types)
   */
  public Map<QName, Serializable> saveMetadata(ContentObject contentObject) {
    Assert.notNull(contentObject, "object must not be null");

    contentObject.beforeSaveCallback();

    CMObject obj = contentObject.getContentObject();
    Assert.notNull(obj, "CMObject must not be null");

    Map<QName, Object> allProps = new HashMap<QName, Object>(obj.getPropertiesMap());

    // resolve unkown properties and put it all together
    Map<String, Object> unkwonProperties = contentObject.getUnkwonProperties();
    for (Entry<String, Object> entry : unkwonProperties.entrySet()) {
      QName qname = QName.resolveToQName(serviceRegistry.getNamespaceService(), entry.getKey());
      allProps.put(qname, entry.getValue());
    }

    NodeRef toNode = new NodeRef(contentObject.getSysRef());

    if (this.serviceRegistry.getCheckOutCheckInService().getWorkingCopy(toNode) != null) { // works on all alfresco versions
    //works on latest alfresco versions. if (this.serviceRegistry.getCheckOutCheckInService().isCheckedOut(toNode)) {
      if (LockStatus.LOCK_OWNER.equals(this.serviceRegistry.getLockService().getLockStatus(toNode))) {
        toNode = this.serviceRegistry.getCheckOutCheckInService().getWorkingCopy(toNode);
      }
    }

    Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
    for (Entry<QName, Object> entry : allProps.entrySet()) {
      QName qname = entry.getKey();
      if (qname == null) {
        continue;
      }

      PropertyDefinition propertyDef = this.serviceRegistry.getDictionaryService().getProperty(qname);
      if (propertyDef == null) {
        continue;
      }

      if (obj.isReadOnlyProperty(qname)) {
        continue;
      }

      Object value = entry.getValue();

      if (value instanceof String[]) {
        // TODO dgradecak: check if we need to handle props different than
        // String[]
        value = ((String[]) value)[0];
      }

      DataTypeDefinition dataType = propertyDef.getDataType();
      if (DataTypeDefinition.CONTENT.equals(dataType.getName())) {

        if (toNode != null) {
          ContentWriter writer = this.serviceRegistry.getContentService().getWriter(toNode, propertyDef.getName(), true);

          // TODO dgradecak : istestirati ovaj dio
          if (value instanceof MultipartFile) {
            MultipartFile file = (MultipartFile) value;
            writer.setMimetype(this.serviceRegistry.getMimetypeService().guessMimetype(file.getOriginalFilename()));
            try {
              writer.putContent(file.getInputStream());
            }
            catch (IOException e) {
              throw new RuntimeException("Could not write the file input stream", e);
            }
          }
          else if (value instanceof File) {
            File file = (File) value;
            writer.setMimetype(this.serviceRegistry.getMimetypeService().guessMimetype(file.getName()));
            writer.putContent(file);
          }
          else if (value instanceof String) {
            writer.setMimetype("text/plain");
            writer.putContent((String) value);
          }
          else {
            if(value != null) {
              throw new RuntimeException("Unhandled property of type d:content. Name : " + propertyDef.getName());
            }
          }
        }
      }
      else {
        if (DataTypeDefinition.QNAME.equals(dataType.getName())) {
          if (value instanceof String && !StringUtils.hasText((String) value)) {
            continue;
          }
        }

        Object convertedValue = converter.convert(dataType, value);
        properties.put(qname, (Serializable) convertedValue);
      }
    }

    // assure to remove nodeRef property before saving
    properties.remove(ContentModel.PROP_NODE_REF);
    if (!properties.isEmpty()) {
      this.serviceRegistry.getNodeService().addProperties(toNode, properties);
    }
    // syncAspectsAndActions(nodeRef, obj);

    return properties;
  }

  /**
   * Does not handle associations or complex types.
   */
  public void getMetadata(final ContentObject contentObject) {
    Assert.notNull(contentObject, "object must not be null");

    CMObject obj = contentObject.getContentObject();
    Assert.notNull(obj, "CMObject must not be null");

    NodeRef fromNode = new NodeRef(contentObject.getSysRef());

    if (this.serviceRegistry.getCheckOutCheckInService().getWorkingCopy(fromNode) != null) { // works on all alfresco versions
    // works on latest alfresco versions. if (this.serviceRegistry.getCheckOutCheckInService().isCheckedOut(fromNode)) {
      if (LockStatus.LOCK_OWNER.equals(this.serviceRegistry.getLockService().getLockStatus(fromNode))) {
        fromNode = this.serviceRegistry.getCheckOutCheckInService().getWorkingCopy(fromNode);
      }
    }

    // TODO dgradecak: ako se pokaze mozda korisiti introspekciju za samo one
    // metapodatke koji su dohvaceni kroz getter
    Map<QName, Serializable> properties = this.serviceRegistry.getNodeService().getProperties(fromNode);

    Map<QName, Object> convertedProps = new HashMap<QName, Object>();
    for (Entry<QName, Serializable> entry : properties.entrySet()) {
      QName qName = entry.getKey();

      PropertyDefinition def = this.serviceRegistry.getDictionaryService().getProperty(qName);

      // Skip when there is no property definition. This might be the case if
      // property was renamed.
      if (def == null) {
        continue;
      }

      DataTypeDefinition dataType = def.getDataType();

      Serializable value = entry.getValue();
      if (DataTypeDefinition.CONTENT.equals(dataType.getName())) {
        ContentReader reader = this.serviceRegistry.getContentService().getReader(fromNode, qName);

        if (reader != null) {
          String mimetype = reader.getMimetype();
          if (this.serviceRegistry.getMimetypeService().isText(mimetype)) {
            value = reader.getContentString();
          }
          else {
            // TODO dgradecak : recheck this!!!
            value = reader.getContentUrl();
          }
        }
      }

      convertedProps.put(qName, value);
    }

    obj.setProperties(convertedProps);

    syncAspectsAndActions(fromNode, obj);
  }

  private void syncAspectsAndActions(NodeRef fromNode, CMObject obj) {
    Set<QName> aspects = this.serviceRegistry.getNodeService().getAspects(fromNode);
    List<QName> cmAspects = new ArrayList<QName>(aspects);

    Collections.sort(cmAspects);
    obj.setAspects(cmAspects);

    // bind actions
    List<Action> actions = obj.getActions();
    if (actions != null) {
      for (Action action : actions) {
        autowiredAnnotationBeanPostProcessor.processInjection(action);
      }
    }
  }

  public void setServiceRegistry(ServiceRegistry serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
  }

  public void setAutowiredAnnotationBeanPostProcessor(AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor) {
    this.autowiredAnnotationBeanPostProcessor = autowiredAnnotationBeanPostProcessor;
  }

}
